#ifndef SWEGL_MATRIX3_H
#define SWEGL_MATRIX3_H

namespace swegl
{

class Matrix3x3
{
public:
	Matrix3x3();
	Matrix3x3(const Vec3 &x, const Vec3 &y, const Vec3 &z);
	// row by row
	Matrix3x3(const float xx, const float xy, const float xz,
	          const float yx, const float yy, const float yz,
	          const float zx, const float zy, const float zz);

	const Vec3 &operator[](uint rowIndex) const;
	Vec3 &operator[](uint rowIndex);
	Vec3 column(uint index) const;

	Matrix3x3 operator * (const Matrix3x3 &rhs) const;
	Matrix3x3 operator + (const Matrix3x3 &rhs) const;
	Matrix3x3 operator - (const Matrix3x3 &rhs) const;

	Matrix3x3 &operator *= (const Matrix3x3 &rhs);
	Matrix3x3 &operator += (const Matrix3x3 &rhs);
	Matrix3x3 &operator -= (const Matrix3x3 &rhs);

	// M * v
	Vec3 operator * (const Vec3 &v) const;
	// v * M = M^-1 * v
	//friend Vec3 operator * (const Vec3 &v, const Matrix3x3 &matrix);

	bool operator == (const Matrix3x3 &rhs) const;
	bool operator != (const Matrix3x3 &rhs) const;
	bool isEqual(const Matrix3x3 &rhs) const;
	bool isEqual(const Matrix3x3 &rhs, float epsilon) const;

	Matrix3x3 &transpose();
	static Matrix3x3 Transpose(const Matrix3x3 &m);

	bool inverse();

	// set the rotation part of the matrix
	void setRotationX(float angle);
	void setRotationY(float angle);
	void setRotationZ(float angle);
	void setRotation(const Vec3 &axis, float angle);
	
	// cast operator
	const float *toPtr() const;
	float *toPtr();

	static Matrix3x3 RotationX(float angle);
	static Matrix3x3 RotationY(float angle);
	static Matrix3x3 RotationZ(float angle);
	static Matrix3x3 Rotation(const Vec3 &axis, float angle);

	static const Matrix3x3 Zero;
	static const Matrix3x3 Identity;

private:
	Vec3 m[3];
};

inline Matrix3x3::Matrix3x3()
{
}

inline Matrix3x3::Matrix3x3(const Vec3 &x, const Vec3 &y, const Vec3 &z)
{
	m[0] = x;
	m[1] = y;
	m[2] = z;
}

inline Matrix3x3::Matrix3x3(const float xx, const float xy, const float xz, const float yx, const float yy, const float yz, const float zx, const float zy, const float zz)
{
	m[0].x = xx;
	m[0].y = xy;
	m[0].z = xz;
	m[1].x = yx;
	m[1].y = yy;
	m[1].z = yz;
	m[2].x = zx;
	m[2].y = zy;
	m[2].z = zz;
}

inline Vec3 Matrix3x3::column(uint index) const
{
	return Vec3(m[0][index], m[1][index], m[2][index]);
}

inline const Vec3 &Matrix3x3::operator[](uint rowIndex) const
{
	return m[rowIndex];
}

inline Vec3 &Matrix3x3::operator[](uint rowIndex)
{
	return m[rowIndex];
}

inline Vec3 Matrix3x3::operator * (const Vec3 &v) const
{
	return m[0] * v.x + m[1] * v.y + m[2] * v.z;
}

inline bool Matrix3x3::operator == (const Matrix3x3 &rhs) const
{
	return isEqual(rhs);
}

inline bool Matrix3x3::operator != (const Matrix3x3 &rhs) const
{
	return ! isEqual(rhs);
}

inline bool Matrix3x3::isEqual(const Matrix3x3 &rhs) const
{
	return m[0] == rhs.m[0] && m[1] == rhs.m[1] && m[2] == rhs.m[2];
}

inline bool Matrix3x3::isEqual(const Matrix3x3 &rhs, float epsilon) const
{
	return Vec3::IsAlmostEqual(m[0], rhs.m[0], epsilon)
	       && Vec3::IsAlmostEqual(m[1], rhs.m[1], epsilon)
	       && Vec3::IsAlmostEqual(m[2], rhs.m[2], epsilon);
}

inline const float *Matrix3x3::toPtr() const
{
	return m[0].toPtr();
}

inline float *Matrix3x3::toPtr()
{
	return m[0].toPtr();
}

inline Matrix3x3 Matrix3x3::RotationX(float angle)
{
	Matrix3x3 m;
	m.setRotationX(angle);
	return m;
}

inline Matrix3x3 Matrix3x3::RotationY(float angle)
{
	Matrix3x3 m;
	m.setRotationY(angle);
	return m;
}

inline Matrix3x3 Matrix3x3::RotationZ(float angle)
{
	Matrix3x3 m;
	m.setRotationZ(angle);
	return m;
}

inline Matrix3x3 Matrix3x3::Rotation(const Vec3 &axis, float angle)
{
	Matrix3x3 m;
	m.setRotation(axis, angle);
	return m;
}

}

#endif
